# 手写IOC DI

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
相关源码参考: 手写Spring代码仓库 (opens new window)


# 一、IOC & DI 流程

# IOC 流程

IOC 负责管理 Bean 的生命周期和依赖关系,大致步骤如下:

  1. 加载配置文件 读取 Spring 配置文件(如 XML 或注解配置)。
  2. 扫描指定路径 扫描配置文件中指定的包路径,找到所有需要管理的类。
  3. 解析类信息 将扫描到的类通过 BeanDefinitionReader 转为 BeanDefinition 对象,封装类信息、依赖关系、作用域等。
  4. 注册 BeanDefinitionBeanDefinition 注册到容器中,形成 Bean 的定义集合。

# DI 流程

DI 负责将 Bean 之间的依赖关系注入到对象中,主要步骤如下:

  1. 遍历 BeanDefinition 获取容器中所有的 Bean 定义。
  2. 创建并初始化 Bean 实例
    • 通过 BeanDefinition 实例化 Bean 对象。
    • 使用 BeanWrapper 对 Bean 进行封装,以便支持属性注入和 AOP 功能。
    • BeanWrapper 对象缓存到容器中,便于后续获取。
  3. 执行依赖注入 遍历 Bean 的属性,检查依赖,并注入相应的 Bean,实现对象间的解耦。

# 二、自定义配置注解(annotation模块)

在手写过程中,我们通过自定义注解实现依赖注入、控制器注册、请求映射等功能。以下是各注解的说明:

# YymAutowired

/**
 * 自动注入
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YymAutowired {

    String value() default "";
}
1
2
3
4
5
6
7
8
9
10

说明

  • 用于标记需要自动注入的字段。
  • 可以通过 value 指定注入的 Bean 名称,如果为空,则按类型自动匹配。
  • 作用类似 Spring 的 @Autowired

# YymController

/**
 * 将页面交互接口注册到容器,注册接口
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YymController {

	String value() default "";
}
1
2
3
4
5
6
7
8
9
10

说明

  • 用于标记控制器类,将其注册到框架的容器中。
  • value 可指定 Bean 名称,默认为类名首字母小写。
  • 类似 Spring MVC 中的 @Controller

# YymRequestMapping

/**
 * 请求url
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YymRequestMapping {

	String value() default "";
}
1
2
3
4
5
6
7
8
9
10

说明

  • 用于标记类或方法的请求 URL。
  • 支持类级别和方法级别标记,组合形成完整路径。
  • 类似 Spring MVC 中的 @RequestMapping

# YymRequestParam

/**
 * 请求参数映射
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YymRequestParam {

	String value() default "";

	boolean required() default true;
}
1
2
3
4
5
6
7
8
9
10
11
12

说明

  • 用于方法参数,映射请求中的参数值。
  • value 指定参数名,required 指定是否必填。
  • 类似 Spring MVC 中的 @RequestParam

# YymService

/**
 * 将业务逻辑接口注册到容器,注册接口
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YymService {

	String value() default "";
}
1
2
3
4
5
6
7
8
9
10

说明

  • 用于标记业务逻辑类,将其注册到框架容器。
  • value 可指定 Bean 名称,默认为类名首字母小写。
  • 类似 Spring 中的 @Service

# 总结

  • YymController + YymRequestMapping 用于 Web 层路由管理
  • YymService 用于 业务逻辑注册
  • YymAutowired + YymRequestParam 用于 依赖注入和请求参数映射

# 三、对Bean的封装(beans模块)

# YymBeanDefinition

/**
 * 封装Bean定义
 */
public class YymBeanDefinition {

    private String factoryBeanName;
    private String beanClassName;

    public String getFactoryBeanName() {
        return factoryBeanName;
    }

    public void setFactoryBeanName(String factoryBeanName) {
        this.factoryBeanName = factoryBeanName;
    }

    public String getBeanClassName() {
        return beanClassName;
    }

    public void setBeanClassName(String beanClassName) {
        this.beanClassName = beanClassName;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

说明

  • 封装了一个 Bean 的定义信息,包括 Bean 的名字和类名。
  • 在 Spring 中,BeanDefinition 是容器管理 Bean 的核心数据结构,它存储了 Bean 的类型、作用域、依赖信息等。

# YymBeanWrapper

/**
 * 包装模式
 * 在spring中用来获取JavaBean的属性值, 对 JavaBean 的属性进行封装和操作(读写) 的
 */
public class YymBeanWrapper {

    private Object wrapperInstance;
    private Class<?> wrappedClass;

    public YymBeanWrapper(Object instance) {
        this.wrapperInstance = instance;
        this.wrappedClass = instance.getClass();
    }

    public Object getWrapperInstance() {
        return wrapperInstance;
    }

    public Class<?> getWrappedClass() {
        return wrappedClass;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

说明

  • 对 Bean 实例进行包装,使得容器可以统一操作 Bean 的实例和类型信息。
  • 在 Spring 中,BeanWrapper 提供了对 Bean 属性的读取、设置和类型转换功能。

# YymBeanDefinitionReader

/**
 * 从各种配置资源中(如 XML、Properties、注解等)读取 Bean 定义信息,
 * 并生成 BeanDefinition 放入 BeanFactory(或 ApplicationContext)中。
 * 有各种子实现 如 XmlBeanDefinitionReader 等, 目前主流是将配置类中的bean转为BeanDefinition
 */
public class YymBeanDefinitionReader {

    //保存扫描到的beanName
    private List<String> regitryBeanClasses = new ArrayList<>();
    // 将配置文件加载为Properties对象
    private Properties contextConfig = new Properties();

    public YymBeanDefinitionReader(String... configLocations) {
        doLoadConfig(configLocations[0]);

        //读取配置文件中需要扫描的路径
        doScanner(contextConfig.getProperty("scanPackage"));
    }


    public Properties getConfig(){
        return this.contextConfig;
    }

    /**
     * 将配置文件转为 Properties对象
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation.replaceAll("classpath:",""));
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 扫描路径, 将路径下所有的全类名保存至 regitryBeanClasses
     */
    private void doScanner(String scanPackage) {
        //jar 、 war 、zip 、rar
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/"));
        File classPath = new File(url.getFile());

        //当成是一个ClassPath文件夹
        for (File file : classPath.listFiles()) {
            if(file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            }else {
                if(!file.getName().endsWith(".class")){continue;}
                //全类名 = 包名.类名
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                regitryBeanClasses.add(className);
            }
        }
    }

    /**
     * 加载BeanDefinitions
     */
    public List<YymBeanDefinition> loadBeanDefinitions() {
        List<YymBeanDefinition> result = new ArrayList<YymBeanDefinition>();
        try {
            // 1. 遍历所有扫描路径下的 类全名
            for (String className : regitryBeanClasses) {
                Class<?> beanClass = Class.forName(className);
                // 2. 如果是接口跳过, 因为接口无法实例化
                if(beanClass.isInterface()){continue;}
                // 3. 如果是类, 保存类对应的ClassName(全类名), beanName 默认是类名首字母小写
                result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
                // 4. 如果类有父接口, 需要建立接口名 → 实现类名的映射 因为在注入时往往是面向接口编程 注入的是接口
                for (Class<?> i : beanClass.getInterfaces()) {
                    result.add(doCreateBeanDefinition(i.getName(),beanClass.getName()));
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return result;
    }

    private YymBeanDefinition doCreateBeanDefinition(String beanName, String beanClassName) {
        YymBeanDefinition beanDefinition = new YymBeanDefinition();
        beanDefinition.setFactoryBeanName(beanName);
        beanDefinition.setBeanClassName(beanClassName);
        return beanDefinition;
    }


    /**
     * 类首字母小写, 简单写法
     */
    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

说明

  • 从配置文件中读取 Bean 的定义信息,并将其转化为 YymBeanDefinition 对象集合。
  • Spring 有 BeanDefinitionReader(如 XmlBeanDefinitionReader),用于解析 XML、注解或 Java 配置类,并生成 BeanDefinition
  • 设计思想
    • 将资源解析和 Bean 定义生成分离。
    • 支持面向接口编程的依赖注入。
    • 提供可扩展的入口,以便未来支持不同类型的配置源(XML、注解、Properties)。

# 四、IOC容器 (context模块)

# 主要成员变量

  • beanDefinitionMap:存放 Bean 名称到 YymBeanDefinition 的映射,用于描述 Bean 定义信息。
  • factoryBeanObjectCache:一级缓存,存放 Bean 实例,用于单例管理。
  • factoryBeanInstanceCache:二级缓存,可用于存放提前暴露的 Bean 实例(解决循环依赖)。
  • beanDefinitionReader:负责读取配置资源,生成 BeanDefinition。

# 容器初始化流程

  1. 构造器初始化
    • 将配置文件路径传入,创建 YymBeanDefinitionReader
    • beanDefinitionReader 加载配置文件,扫描包并生成 YymBeanDefinition 列表。
  2. 注册 BeanDefinition
    • 遍历 YymBeanDefinition 列表,将每个 BeanDefinition 注册到 beanDefinitionMap
    • 至此,容器完成 Bean 定义信息的加载与注册,形成“伪 IOC 容器”。
  3. Bean 实例化与依赖注入
    • 根据需要初始化 Bean,创建实例并包装为 YymBeanWrapper
    • 完成自动依赖注入(支持按类型或按名称注入)。
    • 将 Bean 实例缓存到 factoryBeanObjectCachefactoryBeanInstanceCache

主要关注 YymApplicationContext 如何初始化

public class YymApplicationContext {

    // 存储BeanDefinition
    protected final Map<String, YymBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    // 单例IOC缓存容器, 原始 Bean 实例池
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>();
    // 通用IOC容器, 存储 Bean 包装器, 包含bean的元数据(如原始 class、注解信息、依赖项等)
    private Map<String, YymBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    private YymBeanDefinitionReader beanDefinitionReader;

    public YymApplicationContext(String... configLocations) {
        try {
            // 1. 定位配置文件
            beanDefinitionReader = new YymBeanDefinitionReader(configLocations);

            // 2. 加载配置文件, 将对应的类组装成BeanDefinition
            List<YymBeanDefinition> beanDefinitions = beanDefinitionReader.loadBeanDefinitions();

            // 3. 注册, 将BeanDefinition注册到容器(伪IOC容器) 至此容器初始化结束
            doRegisterBeanDefinition(beanDefinitions);

            // 4. 完成自动依赖注入
            doAutowrited();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将BeanDefinition注册到容器(伪IOC容器)
     */
    private void doRegisterBeanDefinition(List<YymBeanDefinition> beanDefinitions) throws Exception {

        for (YymBeanDefinition beanDefinition : beanDefinitions) {
            // 防止重复加载
            if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exists");
            }

            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        }
    }

    /**
     * 完成自动依赖注入
     * 本例只涉及非延时加载情况
     */
    private void doAutowrited() {
        for (Map.Entry<String, YymBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            try {
                getBean(beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Object getBean(Class<?> beanClass) throws Exception {
        return getBean(beanClass.getName());
    }

    /**
     * 正式开始依赖注入 DI
     * 1. 读取 BeanDefinition 中的元数据信息(如类名、构造函数、依赖等)
     * 2. 通过反射创建 bean 的实例对象
     * 3. 使用 BeanWrapper 对 bean 进行封装,以便后续属性填充和类型转换等操作
     * <p>
     * 这也就是平时使用 applicationContext.getBean(beanName)获取bean的方法
     * <p>
     * 此处用到装饰器模式, 为后续AOP提供支持
     */
    public Object getBean(String beanName) throws Exception {

        // 1. 读取 BeanDefinition 中的元数据信息
        YymBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        Object instance = null;

        // 2. 实例化
        instance = instantiateBean(beanName, beanDefinition);

        // 3. 使用装饰器模式对 instance 进行装饰
        YymBeanWrapper beanWrapper = new YymBeanWrapper(instance);

        // 4. 将 BeanWrapper 存储到 IOC 容器中
        factoryBeanInstanceCache.put(beanName, beanWrapper);

        // 5. 执行依赖注入
        populateBean(beanName, beanDefinition, beanWrapper);

        return beanWrapper.getWrapperInstance();
    }


    /**
     * 创建真正的实例对象
     */
    private Object instantiateBean(String beanName, YymBeanDefinition beanDefinition) {

        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            if (this.factoryBeanObjectCache.containsKey(beanName)) {
                instance = this.factoryBeanObjectCache.get(beanName);
            } else {
                Class<?> clazz = Class.forName(className);
                //2、默认的类名首字母小写
                instance = clazz.newInstance();
                this.factoryBeanObjectCache.put(beanName, instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    /**
     * 真正的依赖注入 DI
     */
    private void populateBean(String beanName, YymBeanDefinition beanDefinition, YymBeanWrapper beanWrapper) {

        //可能涉及到循环依赖?
        //A{ B b}
        //B{ A b}
        //用两个缓存,循环两次
        //1、把第一次读取结果为空的BeanDefinition存到第一个缓存
        //2、等第一次循环之后,第二次循环再检查第一次的缓存,再进行赋值

        Object instance = beanWrapper.getWrapperInstance();
        // 注入当前类的成员属性 A{ B b} 实际B就是A的成员变量, 需要通过A的class对象获取到对应的字段进行注入
        Class<?> clazz = beanWrapper.getWrappedClass();

        //在Spring中@Component  只有 Autowired 注解标记的字段需要注入
        if (!(clazz.isAnnotationPresent(YymController.class) || clazz.isAnnotationPresent(YymService.class))) {
            return;
        }

        // 把所有的包括private/protected/default/public 修饰字段都取出来
        for (Field field : clazz.getDeclaredFields()) {
            if (!field.isAnnotationPresent(YymAutowired.class)) {
                continue;
            }

            YymAutowired autowired = field.getAnnotation(YymAutowired.class);

            //如果用户没有自定义的beanName,就默认根据类型注入
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                //field.getType().getName() 获取字段的类型
                autowiredBeanName = field.getType().getName();
            }

            //暴力访问
            field.setAccessible(true);

            try {
                if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                    continue;
                }
                //ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public Properties getConfig() {
        return this.beanDefinitionReader.getConfig();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

# 五、定义DispatcherServlet

YymDispatcherServlet 是整个框架的 请求入口,负责将 HTTP 请求分发给对应的处理逻辑。类似于 Spring MVC 中的核心前端控制器。

# 主要流程

  1. 容器初始化
    • init() 方法中,创建 YymApplicationContext,完成 IOC 容器初始化。
    • 加载配置文件,扫描 Bean,注册 BeanDefinition,完成自动依赖注入。
  2. 请求处理
    • doGet() 方法统一委托给 doPost(),保证 GET、POST 请求统一处理。
    • doPost() 方法是核心请求分发逻辑入口,未来会实现:
      • 根据请求 URL 找到对应的 Controller/Handler。
      • 执行方法,处理请求参数。
      • 返回结果给客户端(视图解析或 JSON 响应)。
public class YymDispatcherServlet extends HttpServlet {

    private YymApplicationContext applicationContext;

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 初始化spring IOC 核心容器
        applicationContext = new YymApplicationContext();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 设计亮点

  • 统一入口:所有请求通过一个 Servlet 入口,便于统一管理请求和处理逻辑。
  • 与 IOC 容器结合:DispatcherServlet 可以直接从 applicationContext 获取 Controller Bean,实现依赖注入。
  • 可扩展:后续可加入 HandlerMapping、HandlerAdapter、ViewResolver 等,实现完整 MVC 流程。